// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    [Serializable]
    public partial class BooleanExpressionType : ExpressionType, IBooleanValue
    {
        public static BooleanOperatorsEnum BooleanOperatorsEnumFromString(string value)
        {
            if (string.Compare(value, "EQL", true) == 0)
                return BooleanOperatorsEnum.EQL;
            else if (string.Compare(value, "NEQ", true) == 0)
                return BooleanOperatorsEnum.NEQ;
            else if (string.Compare(value, "GEQ", true) == 0)
                return BooleanOperatorsEnum.GEQ;
            else if (string.Compare(value, "GTR", true) == 0)
                return BooleanOperatorsEnum.GTR;
            else if (string.Compare(value, "LEQ", true) == 0)
                return BooleanOperatorsEnum.LEQ;
            else if (string.Compare(value, "LSS", true) == 0)
                return BooleanOperatorsEnum.LSS;
            else if (string.Compare(value, "CONTAINS", true) == 0)
                return BooleanOperatorsEnum.CONTAINS;
            else if (string.Compare(value, "CONTAINSHTML", true) == 0)
                return BooleanOperatorsEnum.CONTAINSHTML;
            else if (string.Compare(value, "ENDSWITH", true) == 0)
                return BooleanOperatorsEnum.ENDSWITH;
            else if (string.Compare(value, "HASAVALUE", true) == 0)
                return BooleanOperatorsEnum.HASAVALUE;
            else if (string.Compare(value, "LIKEMATCH", true) == 0)
                return BooleanOperatorsEnum.LIKEMATCH;
            else if (string.Compare(value, "EXACTMATCH", true) == 0)
                return BooleanOperatorsEnum.EXACTMATCH;
            else if (string.Compare(value, "MAXLENGTHTEST", true) == 0)
                return BooleanOperatorsEnum.MAXLENGTHTEST;
            else if (string.Compare(value, "MINLENGTHTEST", true) == 0)
                return BooleanOperatorsEnum.MINLENGTHTEST;
            else if (string.Compare(value, "PRECISIONTEST", true) == 0)
                return BooleanOperatorsEnum.PRECISIONTEST;
            else if (string.Compare(value, "STARTSWITH", true) == 0)
                return BooleanOperatorsEnum.STARTSWITH;
            else if (string.Compare(value, "VALIDISBN", true) == 0)
                return BooleanOperatorsEnum.VALIDISBN;
            else if (string.Compare(value, "VALIDUPC", true) == 0)
                return BooleanOperatorsEnum.VALIDUPC;
            else if (string.Compare(value, "VALIDURI", true) == 0)
                return BooleanOperatorsEnum.VALIDURI;
            else if (string.Compare(value, "ALL", true) == 0)
                return BooleanOperatorsEnum.ALL;
            else
                throw new ArgumentException(string.Format("{0} is not a valid BooleanOperatorEnum value", value));
        }

        BooleanOperatorsEnum m_ExpressionOperator;

        public BooleanOperatorsEnum ExpressionOperator
        {
            get { return m_ExpressionOperator; }
            set { m_ExpressionOperator = value; }
        }

        public override CIP4PrimitiveType UnderlyingType
        {
            get { return CIP4PrimitiveType.CIP4Boolean; }
        }

        public BooleanExpressionType()
        {
        }

        public BooleanExpressionType(CIValueType left, BooleanOperatorsEnum expressionOperator, CIValueType right)
        {
            if (left == null)
                throw new ArgumentException("The left operand for a boolean expression cannot be null");
            m_ExpressionOperator = expressionOperator;
            Operands.Add(left);
            if (right != null)
                Operands.Add(right);
        }

        public BooleanExpressionType(BooleanOperatorsEnum expressionOperator, List<CIValueType> expressionOperands)
        {
            m_ExpressionOperator = expressionOperator;
            Operands.SetOperands(expressionOperands);
        }

        public BooleanExpressionType(XElement element)
            : base(element)
        {
            XAttribute operatorAtt = element.Attribute("Operator");
            if (operatorAtt != null)
                ExpressionOperator = BooleanOperatorsEnumFromString(operatorAtt.Value);
        }

        public override void XmlFor(XmlWriter xwrtr, bool writeNamespace)
        {
            xwrtr.WriteStartElement("BooleanExpression");
            if (writeNamespace)
                xwrtr.WriteAttributeString("xmlns", RuleXmlNS);
            writeNamespace = false;
            xwrtr.WriteAttributeString("Operator", ExpressionOperator.ToString());
            foreach (CIValueType val in Operands)
                val.XmlFor(xwrtr, writeNamespace);
            xwrtr.WriteEndElement();
        }

        public override object Evaluate(IDataPacketType data)
        {
            if (data != null && data.PacketStats != null)
                data.PacketStats.BooleanOperatorsCount[(int)ExpressionOperator] += 1;
            switch (ExpressionOperator)
            {
                case BooleanOperatorsEnum.EQL:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == op2)
                            return true;
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is StringAtomType || Operands[0] is StringExpressionType) && (Operands[1] is StringAtomType || Operands[1] is StringExpressionType) ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 ==  (DateTime)op2;
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare(op1.ToString(), op2.ToString(), false) == 0;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            return (bool)op1 == (bool)op2;
                        return System.Convert.ToDouble(op1) == System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.LEQ:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 <= (DateTime)op2;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used in a LEQ expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare((string)op1, (string)op2, false) <= 0;
                        return System.Convert.ToDouble(op1) <= System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.GEQ:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 >= (DateTime)op2;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used in a LEQ expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare((string)op1, (string)op2, false) >= 0;
                        return System.Convert.ToDouble(op1) >= System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.GTR:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 > (DateTime)op2;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used in a LEQ expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare((string)op1, (string)op2, false) > 0;
                        return System.Convert.ToDouble(op1) > System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.LSS:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 < (DateTime)op2;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used in a LEQ expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare((string)op1, (string)op2, false) < 0;
                        return System.Convert.ToDouble(op1) < System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.NEQ:
                    {
                        object op1 = Operands[0].Evaluate(data);
                        object op2 = Operands[1].Evaluate(data);
                        if (op1 == null || op2 == null)
                            return false;
                        if (!(op1.GetType() == op2.GetType() ||
                            (Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) && (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType) ||
                            (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType) && (Operands[1] is DateTimeExpressionType || Operands[1] is DateTimeAtomType)))
                            throw new ArgumentException(string.Format("{0} and {1} cannot be be used together in an equals expression - they are not comparable types", op1.ToString(), op2.ToString()));
                        if (Operands[0] is DateTimeAtomType || Operands[0] is DateTimeExpressionType)
                            return (DateTime)op1 != (DateTime)op2;
                        if (Operands[0] is StringAtomType || Operands[0] is StringExpressionType)
                            return string.Compare((string)op1, (string)op2, false) != 0;
                        if (Operands[0] is BooleanAtomType || Operands[0] is BooleanExpressionType)
                            return (bool)op1 != (bool)op2;
                        return System.Convert.ToDouble(op1) != System.Convert.ToDouble(op2);
                    }
                case BooleanOperatorsEnum.CONTAINS:
                    {
                        return data.Operations.CONTAINS(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.CONTAINSHTML:
                    {
                        return data.Operations.CONTAINSHTML(Operands[0].Evaluate(data));
                    }
                case BooleanOperatorsEnum.ENDSWITH:
                    {
                        return data.Operations.ENDSWITH(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.HASAVALUE:
                    {
                        return data.Operations.HASAVALUE(Operands[0].Evaluate(data));
                    }
                case BooleanOperatorsEnum.LIKEMATCH:
                    {
                        object ignoreCase = Operands.Count > 2 ? Operands[2].Evaluate(data) : false;
                        return data.Operations.LIKEMATCH(Operands[0].Evaluate(data), Operands[1].Evaluate(data), System.Convert.ToBoolean(ignoreCase));
                    }
                case BooleanOperatorsEnum.EXACTMATCH:
                    {
                        object ignoreCase = Operands.Count > 2 ? Operands[2].Evaluate(data) : false;
                        object op1 = Operands[0].Evaluate(data);
                        if (op1 is List<CIValueType>)
                        {
                            foreach (CIValueType op1Value in (List<CIValueType>)op1)
                            {
                                if (!data.Operations.EXACTMATCH(op1Value.Evaluate(data), Operands[1].Evaluate(data), System.Convert.ToBoolean(ignoreCase)))
                                    return false;
                            }
                            return true;
                        }
                        return data.Operations.EXACTMATCH(Operands[0].Evaluate(data), Operands[1].Evaluate(data), System.Convert.ToBoolean(ignoreCase));
                    }
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                    {
                        if (!(Operands[0] is ArithmeticExpressionType || Operands[0] is ArithmeticAtomType ||
                            Operands[0] is StringExpressionType || Operands[0] is StringAtomType))
                            throw new ArgumentException(string.Format("{0} cannot be used in a max length test", Operands[0].ToString()));
                        if (!(Operands[1] is ArithmeticExpressionType || Operands[1] is ArithmeticAtomType))
                            throw new ArgumentException(string.Format("{0} must be numeric", Operands[0].ToString()));
                        return data.Operations.MAXLENGTHTEST(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.MINLENGTHTEST:
                    {
                        if (!(Operands[0] is ArithmeticExpressionType || Operands[0] is ArithmeticAtomType ||
                            Operands[0] is StringExpressionType || Operands[0] is StringAtomType))
                            throw new ArgumentException(string.Format("{0} cannot be used in a max length test", Operands[0].ToString()));
                        if (!(Operands[1] is ArithmeticExpressionType || Operands[1] is ArithmeticAtomType))
                            throw new ArgumentException(string.Format("{0} must be numeric", Operands[0].ToString()));
                        return data.Operations.MINLENGTHTEST(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.PRECISIONTEST:
                    {
                        return data.Operations.PRECISIONTEST(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.STARTSWITH:
                    {
                        return data.Operations.STARTSWITH(Operands[0].Evaluate(data), Operands[1].Evaluate(data));
                    }
                case BooleanOperatorsEnum.VALIDISBN:
                    {
                        return data.Operations.VALIDISBN(Operands[0].Evaluate(data));
                    }
                case BooleanOperatorsEnum.VALIDUPC:
                    {
                        return data.Operations.VALIDUPC(Operands[0].Evaluate(data));
                    }
                case BooleanOperatorsEnum.VALIDURI:
                    {
                        return data.Operations.VALIDURI(Operands[0].Evaluate(data));
                    }
                case BooleanOperatorsEnum.ALL:
                    {
                        return data.Operations.ALL(Operands[0].Evaluate(data), Operands[1]);
                    }
                default:
                    throw new ApplicationException("unexpected boolean operator type encountered: working on " + ToString());
            }
        }

        public override bool Check(IDataPacketType data)
        {
            bool result = true;

            LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
            {
                if (status == MajorEnum.Error)
                    result = false;
                return data.Logger(status, minorStatus, message, paras);
            };

            switch (ExpressionOperator)
            {
                case BooleanOperatorsEnum.EQL:
                case BooleanOperatorsEnum.LEQ:
                case BooleanOperatorsEnum.GEQ:
                case BooleanOperatorsEnum.GTR:
                case BooleanOperatorsEnum.LSS:
                case BooleanOperatorsEnum.NEQ:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "A boolean expression using the {0} operator cannot have more than two operands", new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "A boolean expression using the {0} operator must have exactly two operands", new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!(Operands[0].GetType() == Operands[1].GetType() || 
                            ((Operands[0] is ArithmeticAtomType || Operands[0] is ArithmeticExpressionType) &&
                            (Operands[1] is ArithmeticAtomType || Operands[1] is ArithmeticExpressionType))))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "The operands for the {0} operator must be comparable types", new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.CONTAINS:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is StringAtomType || Operands[1] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the CONTAINS operator must both be strings: {0} - {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.CONTAINSHTML:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than one operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operand for the CONTAINSHTML operator must be a string: {0} - {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                            Operands[0].Check(data);
                        break;
                    }
                case BooleanOperatorsEnum.ENDSWITH:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is StringAtomType || Operands[1] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the ENDSWITH operator must both be strings: {0} - {1}",
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.HASAVALUE:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than one operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                            Operands[0].Check(data);
                        break;
                    }
                case BooleanOperatorsEnum.LIKEMATCH:
                case BooleanOperatorsEnum.EXACTMATCH:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is StringAtomType || Operands[1] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the LIKEMATCH operator must both be strings: {0} - {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            if (Operands[1].Check(data) && Operands[1] is StringAtomType &&
                                ((StringAtomType)Operands[1]).AtomSource == AtomSourceEnum.literal)
                            {
                                string regxs = (string)((StringAtomType)Operands[1]).Evaluate(data);
                                try
                                {
                                    Regex rx = new Regex(regxs);
                                }
                                catch (ArgumentException ex)
                                {
                                    localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                        "The regular expression for the LIKEMATCH operator is not valid: {0} - {1}", 
                                        new object[] { ex.Message, this.ToString() });
                                }
                            }
                        }
                        break;
                    }
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is IntegerAtomType || Operands[1] is ArithmeticExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the MAXLENGTHTEST operator must be a string and an integer or arithmetic expression: {0}", 
                                new object[] { this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.MINLENGTHTEST:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is IntegerAtomType || Operands[1] is ArithmeticExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the MINLENGTHTEST operator must be a string and an integer or arithmetic expression: {0}", 
                                new object[] { this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.PRECISIONTEST:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is DecimalAtomType || Operands[0] is ArithmeticExpressionType) &&
                            (Operands[1] is IntegerAtomType || Operands[1] is ArithmeticExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operands for the PRECISIONTEST operator must be a decimal value or arithmetic expression and an integer or arithmetic expression: {0}", 
                                new object[] {  this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.STARTSWITH:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType) &&
                            (Operands[1] is StringAtomType || Operands[1] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "The operands for the STARTSWITH operator must both be strings: {0}", 
                                new object[] { this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                case BooleanOperatorsEnum.VALIDISBN:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator cannot have more than one operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "The operand for the {0} operator must be a string", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                            Operands[0].Check(data);
                        break;
                    }
                case BooleanOperatorsEnum.VALIDUPC:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, 
                                "A boolean expression using the {0} operator cannot have more than one operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "The operand for the {0} operator must be a string: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                            Operands[0].Check(data);
                        break;
                    }
                case BooleanOperatorsEnum.VALIDURI:
                    {
                        if (Operands.Count > 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator cannot have more than one operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 1)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator must have exactly one operand: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (!((Operands[0] is StringAtomType || Operands[0] is StringExpressionType)))
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "The operand for the {0} operator must be a string: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                            Operands[0].Check(data);
                        break;
                    }
                case BooleanOperatorsEnum.ALL:
                    {
                        if (Operands.Count > 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator cannot have more than two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else if (Operands.Count < 2)
                            localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                                "A boolean expression using the {0} operator must have exactly two operands: {1}", 
                                new object[] { ExpressionOperator.ToString(), this.ToString() });
                        else
                        {
                            Operands[0].Check(data);
                            Operands[1].Check(data);
                        }
                        break;
                    }
                default:
                    {
                        localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure,
                            "{0} is not a recognized boolean operator: {1}", 
                            new object[] { ExpressionOperator.ToString(), this.ToString() });
                        break;
                    }
            }
            return result;
        }

        public bool Value(IDataPacketType data)
        {
            return (bool)Evaluate(data);
        }

        public virtual List<string> ComparesTo(string name)
        {
            if (ExpressionOperator == BooleanOperatorsEnum.EQL && 
                Operands[0] is AtomType && ((AtomType)Operands[0]).AtomSource == AtomSourceEnum.namedValue &&
                ((AtomType)Operands[0]).AtomName.EndsWith(name) && Operands[1] is AtomType &&
                ((AtomType)Operands[1]).AtomSource == AtomSourceEnum.literal)
            {
                object value = Operands[1].Evaluate(new RulesEngineDataPacket());
                if (value != null)
                    return new List<string> { value.ToString() };
            }
            if (ExpressionOperator == BooleanOperatorsEnum.EQL &&
                Operands[1] is AtomType && ((AtomType)Operands[1]).AtomSource == AtomSourceEnum.namedValue &&
                ((AtomType)Operands[1]).AtomName.EndsWith(name) && Operands[0] is AtomType &&
                ((AtomType)Operands[0]).AtomSource == AtomSourceEnum.literal)
            {
                object value = Operands[0].Evaluate(new RulesEngineDataPacket());
                if (value != null)
                    return new List<string> { value.ToString() };
            }
            return new List<string>();
        }

        protected string GetSqlOpndString(CIValueType opnd)
        {
            return opnd.ToString(ExpressionFormatEnum.sql);
        }

        private string ToSqlString()
        {
            string leftOp = Operands.Count > 0 ? GetSqlOpndString(Operands[0]) : "InvalidOPerand";
            string rightOp = Operands.Count > 1 ? GetSqlOpndString(Operands[1]) : "InvalidOPerand";
            string otherOp = Operands.Count > 2 ? GetSqlOpndString(Operands[2]) : "InvalidOPerand";
            switch (ExpressionOperator)
            {
                case BooleanOperatorsEnum.EQL:
                    return "(" + leftOp + ")=(" + rightOp + ")";
                case BooleanOperatorsEnum.GEQ:
                    return "(" + leftOp + ")>=(" + rightOp + ")";
                case BooleanOperatorsEnum.GTR:
                    return "(" + leftOp + ")>(" + rightOp + ")";
                case BooleanOperatorsEnum.LEQ:
                    return "(" + leftOp + ")<=(" + rightOp + ")";
                case BooleanOperatorsEnum.LSS:
                    return "(" + leftOp + ")<(" + rightOp + ")";
                case BooleanOperatorsEnum.NEQ:
                    return "(" + leftOp + ")<>(" + rightOp + ")";
                case BooleanOperatorsEnum.CONTAINS:
                    return "(" + leftOp + ") LIKE '%' + (" + rightOp + ") + '%'";
                case BooleanOperatorsEnum.ENDSWITH:
                    return "(" + leftOp + ") LIKE '%' + (" + rightOp + ")";
                case BooleanOperatorsEnum.HASAVALUE:
                    return "(" + leftOp + ") IS NOT NULL";
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                    return "LEN(" + leftOp + ") <= (" + rightOp + ")";
                case BooleanOperatorsEnum.MINLENGTHTEST:
                    return "LEN(" + leftOp + ") >= (" + rightOp + ")";
                case BooleanOperatorsEnum.STARTSWITH:
                    return "(" + leftOp + ") LIKE (" + rightOp + ") + '%'";
                case BooleanOperatorsEnum.PRECISIONTEST:
                case BooleanOperatorsEnum.CONTAINSHTML:
                case BooleanOperatorsEnum.LIKEMATCH:
                case BooleanOperatorsEnum.EXACTMATCH:
                case BooleanOperatorsEnum.VALIDISBN:
                case BooleanOperatorsEnum.VALIDUPC:
                case BooleanOperatorsEnum.VALIDURI:
                case BooleanOperatorsEnum.ALL:
                    return "(1 = 1)";  // For any clause that cannot be evaluated return true so the constructed 
                                       // expression result will always be a supoerset of the original expression
                default:
                    return "InvalidOperand";
            }
        }

        public override string ToString(ExpressionFormatEnum format)
        {
            switch (format)
            {
                case ExpressionFormatEnum.text:
                    return ToString();
                case ExpressionFormatEnum.sql:
                    return ToSqlString();
                case ExpressionFormatEnum.javascript:
                    return ToJavaScriptString();
                default:
                    throw new InvalidExpressionException("Unhandled expression format:" + format);
            }
        }

        public override string ToString()
        {
            string leftOp = Operands.Count > 0 ? Operands[0].ToString() : "InvalidOPerand";
            string rightOp = Operands.Count > 1 ? Operands[1].ToString() : "InvalidOPerand";
            string otherOp = Operands.Count > 2 ? Operands[2].ToString() : "InvalidOPerand";
            string opName = "";
            // Kind of odd - do the infix ops first and return inplace - 
            // for prefix ops, pick up the name and build the return string outside the switch statement
            switch (ExpressionOperator)
            {
                case BooleanOperatorsEnum.EQL:
                    return "(" + leftOp + ")=(" + rightOp + ")";
                case BooleanOperatorsEnum.GEQ:
                    return "(" + leftOp + ")>=(" + rightOp + ")";
                case BooleanOperatorsEnum.GTR:
                    return "(" + leftOp + ")>(" + rightOp + ")";
                case BooleanOperatorsEnum.LEQ:
                    return "(" + leftOp + ")<=(" + rightOp + ")";
                case BooleanOperatorsEnum.LSS:
                    return "(" + leftOp + ")<(" + rightOp + ")";
                case BooleanOperatorsEnum.NEQ:
                    return "(" + leftOp + ")<>(" + rightOp + ")";
                case BooleanOperatorsEnum.CONTAINS:
                case BooleanOperatorsEnum.CONTAINSHTML:
                case BooleanOperatorsEnum.ENDSWITH:
                case BooleanOperatorsEnum.HASAVALUE:
                case BooleanOperatorsEnum.LIKEMATCH:
                case BooleanOperatorsEnum.EXACTMATCH:
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                case BooleanOperatorsEnum.MINLENGTHTEST:
                case BooleanOperatorsEnum.PRECISIONTEST:
                case BooleanOperatorsEnum.STARTSWITH:
                case BooleanOperatorsEnum.VALIDISBN:
                case BooleanOperatorsEnum.VALIDUPC:
                case BooleanOperatorsEnum.VALIDURI:
                case BooleanOperatorsEnum.ALL:
                    opName = ExpressionOperator.ToString();
                    break;
                default:
                    opName = "InvalidOperand";
                    break;
            }
            string result = "Call " + opName + "(";
            string sep = "";
            foreach (CIValueType value in Operands)
            {
                result += sep + value.ToString();
                sep = ", ";
            }
            return result + ")";
        }

        private string ToJavaScriptString()
        {
            if (Operands.Count != 2)
                throw new InvalidExpressionException(Operands.Count + "operands for BooleanExpressionType.");
            string leftOp = Operands[0].ToString(ExpressionFormatEnum.javascript);
            string rightOp = Operands[1].ToString(ExpressionFormatEnum.javascript);
            switch (ExpressionOperator)
            {
                case BooleanOperatorsEnum.EQL:
                    return "(" + leftOp + ")==(" + rightOp + ")";
                case BooleanOperatorsEnum.GEQ:
                    return "(" + leftOp + ")>=(" + rightOp + ")";
                case BooleanOperatorsEnum.GTR:
                    return "(" + leftOp + ")>(" + rightOp + ")";
                case BooleanOperatorsEnum.LEQ:
                    return "(" + leftOp + ")<=(" + rightOp + ")";
                case BooleanOperatorsEnum.LSS:
                    return "(" + leftOp + ")<(" + rightOp + ")";
                case BooleanOperatorsEnum.NEQ:
                    return "(" + leftOp + ")!=(" + rightOp + ")";
                case BooleanOperatorsEnum.STARTSWITH:
                    return "(" + leftOp + ").toString().toLowerCase().indexOf((" + rightOp + ").toString().toLowerCase())==0";
                case BooleanOperatorsEnum.ENDSWITH:
                    return "(" + leftOp + ").toString().toLowerCase().lastIndexOf((" + rightOp + ").toString().toLowerCase())==0";
                case BooleanOperatorsEnum.MINLENGTHTEST:
                    return "(" + leftOp + ").length>=(" + rightOp + ")";
                case BooleanOperatorsEnum.MAXLENGTHTEST:
                    return "(" + leftOp + ").length<=(" + rightOp + ")";
                case BooleanOperatorsEnum.CONTAINS:
                    return "(" + leftOp + ").toLowerCase().indexOf((" + rightOp + ").toLowerCase())>=0";
                case BooleanOperatorsEnum.LIKEMATCH:
                    return "new RegExp(" + rightOp + ").match(" + leftOp + ")!=\"\"";
                case BooleanOperatorsEnum.EXACTMATCH:
                    return "new RegExp('^'+" + rightOp + "+'$').match(" + leftOp + ")!=\"\"";
                default:
                    throw new InvalidExpressionException("Unhandled operator: " + ExpressionOperator);
            }
        }
    }
}
